/*
 * Copyright (c) 2022, IMMORTA Inc.
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without modification,
 * are permitted provided that the following conditions are met:
 *
 * - Redistributions of source code must retain the above copyright notice, this list
 *   of conditions and the following disclaimer.
 *
 * - Redistributions in binary form must reproduce the above copyright notice, this
 *   list of conditions and the following disclaimer in the documentation and/or
 *   other materials provided with the distribution.
 *
 * - Neither the name of IMMORTA Inc. nor the names of its
 *   contributors may be used to endorse or promote products derived from this
 *   software without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
 * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
 * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR
 * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
 * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
 * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
 * ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
 * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 */

#ifndef SPI_DRV_H
#define SPI_DRV_H

/*!
 * @file spi_drv.h
 * @brief This file declares spi driver interfaces
 */

/*!
 * @addtogroup spi_drv
 * @{
 */

/*******Includes***************************************************************/
#include <stdbool.h>
#include "errcodes.h"
#include "os_adapter.h"

/*******Definitions************************************************************/
/*!
 * @brief SPI operation mode, master or slave
 */
typedef enum {
    SPI_MODE_SLAVE  = 0U, /*!< SPI operation mode is slave */
    SPI_MODE_MASTER = 1U  /*!< SPI operation mode is master */
} spi_mode_t;

/*!
 * @brief Pin configuration
 */
typedef enum {
    SPI_PIN_SOUT_MISO_SIN_MOSI = 0U, /*!< SOUT is MISO, SIN is MOSI */
    SPI_PIN_SOUT_MOSI_SIN_MISO = 1U  /*!< SOUT is MOSI, SIN is MISO */
} spi_pin_config_t;

/*!
 * @brief Choose CS signal if support multiple slaves
 */
typedef enum {
    SPI_CS0 = 0U /*!< Choose CS0 */
} spi_cs_t;

/*!
 * @brief CS polarity
 */
typedef enum {
    SPI_CS_POL_LOW  = 0U, /*!< CS active level is LOW */
    SPI_CS_POL_HIGH = 1U  /*!< CS active level is HIGH */
} spi_cs_pol_t;

/*!
 * @brief Clock polarity
 */
typedef enum {
    SPI_CLK_POL_LOW  = 0U, /*!< SCK level is LOW at idle */
    SPI_CLK_POL_HIGH = 1U  /*!< SCK level is HIGH at idle */
} spi_clk_pol_t;

/*!
 * @brief Clock phase
 */
typedef enum {
    SPI_CLK_PHA_1ST = 0U, /*!< SPI data is captured at first SCK edge */
    SPI_CLK_PHA_2ND = 1U  /*!< SPI data is captured at second SCK edge */
} spi_clk_pha_t;

/*!
 * @brief Frame size, 8bit or 16bit
 */
typedef enum {
#if (CONFIG_SPI_NEW_FRAMESIZE_FEATURE == 1U)
    SPI_FRAME_SIZE_8BIT  = 7U,   /*!< Frame size is 8 bits */
    SPI_FRAME_SIZE_16BIT = 15U,  /*!< Frame size is 16 bits */
    SPI_FRAME_SIZE_32BIT = 31U   /*!< Frame size is 32 bits */
#else
    SPI_FRAME_SIZE_8BIT  = 0U,   /*!< Frame size is 8 bits */
    SPI_FRAME_SIZE_16BIT = 1U,   /*!< Frame size is 16 bits */
#endif
} spi_frame_size_t;

/*!
 * @brief Transfer data order, LSB or MSB
 */
typedef enum {
    SPI_TRANSFER_ORDER_LSB = 0U, /*!< SPI transfer data in LSB */
    SPI_TRANSFER_ORDER_MSB = 1U  /*!< SPI transfer data in MSB */
} spi_transfer_order_t;

/*!
 * @brief Sample delay in master mode
 */
typedef enum {
    SPI_SAMPLE_DELAY_NONE        = 0U, /*!< Master sample delay: no delay */
    SPI_SAMPLE_DELAY_ONE_CYCLE   = 1U, /*!< Master sample delay: 1 clock cycle */
    SPI_SAMPLE_DELAY_TWO_CYCLE   = 2U, /*!< Master sample delay: 2 clock cycles */
    SPI_SAMPLE_DELAY_THREE_CYCLE = 3U  /*!< Master sample delay: 3 clock cycles */
} spi_sample_delay_t;

/*!
 * @brief Transfer type, interrupt or dma
 */
typedef enum {
    SPI_USING_INTERRUPT = 0U, /*!< SPI transfers by interrupt */
    SPI_USING_DMA       = 1U, /*!< SPI transfers by DMA */
    SPI_USING_POLLING   = 2U /*!< SPI transfers by POLLING */
} spi_transfer_type_t;

/*!
 * @brief Transfer status
 */
typedef enum {
    SPI_TRANSFER_OK    = 0U, /*!< SPI transfer OK */
    SPI_TRANSMIT_ERROR = 1U, /*!< Error during transmitting */
    SPI_RECEIVE_ERROR  = 2U  /*!< Error during receiving */
} spi_transfer_status_t;

/*!
 * @brief Define the enum of the events which can trigger SPI callback
 */
typedef enum {
    SPI_EVENT_END_TRANSFER = 0U, /*!< End transfer event */
    SPI_EVENT_TX_ERROR     = 1U, /*!< Transmit error event */
    SPI_EVENT_RX_ERROR     = 2U  /*!< Receive error event */
} spi_event_t;

/*!
 * @brief SPI baudrate configuration
 *        baudrate = fclk / (sckHigh + 1 + sckLow + 1)
 */
typedef struct {
    uint8_t sckHigh; /*!< SCK high time */
    uint8_t sckLow;  /*!< SCK low time */
} spi_baud_config_t;

/*!
 * @brief CS timing parameters
 */
typedef struct {
    uint8_t csSetup; /*!< Chip select setup time: (csSetup + 1) fclk */
    uint8_t csHold;  /*!< Chip select hold time: (csHold + 1) fclk */
    uint8_t csIdle;  /*!< Chip select idle time: (csIdle + 1) fclk */
} spi_cs_timing_t;

/*!
 * @brief SPI Callback type
 */
typedef void (*spi_callback_t)(uint32_t instance, spi_event_t event, void* param);

/*!
 * @brief SPI runtime configuations
 */
typedef struct {
    spi_frame_size_t frameSize;            /*!< Select frame size: 8 bits or 16 bits */
    uint16_t bytesPerFrame;                /*!< Number of bytes per frame, 1 byte or 2 bytes */
    bool isContinuousMode;                 /*!< Transfer in continuous mode */
    bool isMaster;                         /*!< Indicates transfer as master or slave*/
    bool isBlocking;                       /*!< Indicates transfer blocking or not */
    spi_baud_config_t baudConfig;          /*!< Baudrate configuarion */
    volatile bool isTransferInProgress;    /*!< True if there is an active transfer */
    const uint8_t* txBuff;                 /*!< Transmit Buffer */
    uint8_t* rxBuff;                       /*!< Receive Buffer */
    uint32_t txDummyData;                  /*!< dummy transfer data */
    volatile uint32_t txSize;              /*!< Number of bytes remaining to transmit */
    volatile uint32_t rxSize;              /*!< Number of bytes remaining to receive */
    volatile spi_transfer_order_t txOrder; /*!< Transmit order, LSB or MSB */
    volatile spi_transfer_order_t rxOrder; /*!< Receive order, LSB or MSB */
    spi_transfer_type_t transferType;      /*!< Select transfer type: interrupt, DMA */
    semaphore_t spiSemaphore;              /*!< The semaphore used for blocking transfers */
    spi_transfer_status_t status;          /*!< Transfer status */
    uint8_t txDMAChannel;                  /*!< Transmit DMA channel */
    uint8_t rxDMAChannel;                  /*!< Receive DMA channel */
    spi_callback_t callback;               /*!< SPI callback function when transfer complete */
    void* callbackParam;                   /*!< SPI callback parameters if necessary */
} spi_state_t;

/*!
 * @brief SPI master configurations
 */
typedef struct {
    bool isContinuousMode;            /*!< Transfer in continuous mode */
    spi_pin_config_t pinConfig;       /*!< Pin configuration */
    spi_baud_config_t baudConfig;     /*!< Baudrate configuarion */
    spi_cs_timing_t csTiming;         /*!< Chip selection timing parameters */
    spi_cs_t csPin;                   /*!< Select CS pin */
    spi_cs_pol_t csPol;               /*!< CS polarity */
    spi_clk_pol_t clkPol;             /*!< Clock polarity */
    spi_clk_pha_t clkPha;             /*!< Clock phase */
    spi_frame_size_t frameSize;       /*!< Select frame size, 8 bits or 16 bits */
    spi_transfer_order_t txOrder;     /*!< Transmit order, LSB or MSB */
    spi_transfer_order_t rxOrder;     /*!< Receive order, LSB or MSB */
    spi_sample_delay_t sampleDelay;   /*!< Sample delay */
    spi_transfer_type_t transferType; /*!< Select transfer type: interrupt, DMA, Polling */
    uint8_t txDMAChannel;             /*!< Transmit DMA channel */
    uint8_t rxDMAChannel;             /*!< Receive DMA channel */
    spi_callback_t callback;          /*!< SPI callback function when transfer complete */
    void* callbackParam;              /*!< SPI callback parameters if necessary */
} spi_master_config_t;

/*!
 * @brief SPI slave configurations
 */
typedef struct {
    spi_pin_config_t pinConfig;       /*!< Pin configuration */
    spi_cs_t csPin;                   /*!< Select CS pin */
    spi_cs_pol_t csPol;               /*!< CS polarity */
    spi_clk_pol_t clkPol;             /*!< Select clock polarity */
    spi_clk_pha_t clkPha;             /*!< Select clock phase */
    spi_frame_size_t frameSize;       /*!< Select frame size, 8 bits or 16 bits */
    spi_transfer_order_t txOrder;     /*!< Transmit order, LSB or MSB */
    spi_transfer_order_t rxOrder;     /*!< Receive order, LSB or MSB */
    spi_transfer_type_t transferType; /*!< Select transfer method: interrupt, DMA, Polling */
    uint8_t txDMAChannel;             /*!< Transmit DMA channel */
    uint8_t rxDMAChannel;             /*!< Receive DMA channel */
    spi_callback_t callback;          /*!< SPI callback function when transfer complete */
    void* callbackParam;              /*!< SPI callback parameters if necessary */
} spi_slave_config_t;

/*******APIs*******************************************************************/
#if defined(__cplusplus)
extern "C" {
#endif

/*!
 * @brief Init SPI instance as a master
 *
 * @param[in] instance: SPI instance
 * @param[out] spiState: SPI state
 * @param[in] spiConfig: SPI master configuration
 * @return ERR_SUCCESS: Init success
 *         ERR_ERROR: Init fail
 */
errcode_t SPI_MasterInit(uint32_t instance,
                         spi_state_t* spiState,
                         const spi_master_config_t* spiConfig);

/*!
 * @brief De-Init a SPI instance
 *
 * @param[in] instance: SPI instance
 * @return ERR_SUCCESS: De-Init success
 *         ERR_ERROR: De-Init fail
 */
errcode_t SPI_MasterDeinit(uint32_t instance);

/*!
 * @brief Get a default configuration of SPI master
 *
 * @param[out] spiConfig: SPI master configuration
 * @return None
 */
void SPI_MasterGetDefaultConfig(spi_master_config_t* spiConfig);

/*!
 * @brief Start a blocking transfer for SPI master
 *
 * @param[in] instance: SPI instance
 * @param[in] txBuffer: Transmit buffer of SPI master
 * @param[in] rxBuffer: Receive buffer of SPI master
 * @param[in] transferSize: Transfer size in byte
 * @param[in] timeout: Timeout value
 * @return ERR_SUCCESS: The transfer is success
 *         ERR_BUSY: Cannot perform transfer because of a transfer is already in progress
 *         ERR_TIMEOUT: The transfer is timeout and aborted
 */
errcode_t SPI_MasterTransferBlocking(uint32_t instance,
                                     const uint8_t* txBuffer,
                                     uint8_t* rxBuffer,
                                     uint32_t transferSize,
                                     uint32_t timeout);

/*!
 * @brief Start a non-blocking transfer for SPI master
 *
 * @param[in] instance: SPI instance
 * @param[in] txBuffer: Transmit buffer of SPI master
 * @param[in] rxBuffer: Receive buffer of SPI master
 * @param[in] transferSize: Transfer size in byte
 * @return ERR_SUCCESS: The transfer is success
 *         ERR_BUSY: Cannot perform transfer because of a transfer is already in progress
 */
errcode_t SPI_MasterTransfer(uint32_t instance,
                             const uint8_t* txBuffer,
                             uint8_t* rxBuffer,
                             uint32_t transferSize);

/*!
 * @brief Start polling transfer for SPI master
 *
 * @param[in] instance: SPI instance
 * @param[in] txBuffer: Transmit buffer of SPI master
 * @param[in] rxBuffer: Receive buffer of SPI master
 * @param[in] transferSize: Transfer size in byte
 * @return ERR_SUCCESS: The transfer is success
 *         ERR_BUSY: Cannot perform transfer because of a transfer is already in progress
 */
errcode_t SPI_MasterTransferPolling(uint32_t instance,
                                    const uint8_t* txBuffer,
                                    uint8_t* rxBuffer,
                                    uint32_t transferSize);

/*!
 * @brief Get transfer status in non-blocking transfer: in progress or complete
 *
 * @param[in] instance: SPI instance
 * @param[out] bytesRemained: Pointer to a value that is filled in with the number of bytes
 *                            that must be received
 * @return ERR_SUCCESS: The transfer has completed successfully
 *         ERR_BUSY: The transfer is still in progress
 */
errcode_t SPI_MasterGetTransferStatus(uint32_t instance, uint32_t* bytesRemained);

/*!
 * @brief Terminates a transfer during progress
 *
 * @param[in] instance: SPI instance
 * @return None.
 */
void SPI_MasterAbortTransfer(uint32_t instance);

/*!
 * @brief Set delay parameters
 *
 * @param[in] instance: SPI instance
 * @param[in] delayParam: Pointer to SPI delay parameters configuration
 * @return None
 */
errcode_t SPI_MasterSetDelay(uint32_t instance, const spi_cs_timing_t* csTiming);

/*!
 * @brief Set chip select pin
 *
 * @param[in] instance: SPI instance
 * @param[in] csNum: Chip select pin to be applied
 * @param[in] csPol: Chip select pin's polarity or active level
 * @return None
 */
void SPI_MasterSetCS(uint32_t instance, spi_cs_t csNum, spi_cs_pol_t csPol);

/*!
 * @brief De-Assert or release current chip select pin
 *        One can call this function to release CS pin in continuous mode manually
 *
 * @param[in] instance: SPI instance
 * @return None
 */
void SPI_MasterReleaseCS(uint32_t instance);

/*!
 * @brief Init SPI instance as a slave
 *
 * @param[in] instance: SPI instance
 * @param[out] spiState: SPI state
 * @param[in] spiConfig: SPI slave configuration
 * @return ERR_SUCCESS: Init success
 *         ERR_ERROR: Init fail
 */
errcode_t SPI_SlaveInit(uint32_t instance,
                        spi_state_t* spiState,
                        const spi_slave_config_t* spiConfig);

/*!
 * @brief De-Init a SPI instance
 *
 * @param[in] instance: SPI instance
 * @return ERR_SUCCESS: De-Init success
 *         ERR_ERROR: De-Init fail
 */
errcode_t SPI_SlaveDeinit(uint32_t instance);

/*!
 * @brief Get a default configuration of SPI slave
 *
 * @param[out] spiConfig: SPI slave configuration
 * @return None
 */
void SPI_SlaveGetDefaultConfig(spi_slave_config_t* spiConfig);

/*!
 * @brief Start a blocking transfer for SPI slave
 *
 * @param[in] instance: SPI instance
 * @param[in] txBuffer: Transmit buffer of SPI slave
 * @param[in] rxBuffer: Receive buffer of SPI slave
 * @param[in] transferSize: SPI instance
 * @param[in] timeout: Timeout value
 * @return ERR_SUCCESS: The transfer is success
 *         ERR_BUSY: Cannot perform transfer because of a transfer is already in progress
 *         ERR_TIMEOUT: The transfer is timeout and aborted
 */
errcode_t SPI_SlaveTransferBlocking(uint32_t instance,
                                    const uint8_t* txBuffer,
                                    uint8_t* rxBuffer,
                                    uint32_t transferSize,
                                    uint32_t timeout);

/*!
 * @brief Start a non-blocking transfer for SPI slave
 *
 * @param[in] instance: SPI instance
 * @param[in] txBuffer: Transmit buffer of SPI slave
 * @param[in] rxBuffer: Receive buffer of SPI slave
 * @param[in] transferSize: SPI instance
 * @return ERR_SUCCESS: The transfer is success
 *         ERR_BUSY: Cannot perform transfer because of a transfer is already in progress
 */
errcode_t SPI_SlaveTransfer(uint32_t instance,
                            const uint8_t* txBuffer,
                            uint8_t* rxBuffer,
                            uint32_t transferSize);

/*!
 * @brief Start polling transfer for SPI slave
 *
 * @param[in] instance: SPI instance
 * @param[in] txBuffer: Transmit buffer of SPI slave
 * @param[in] rxBuffer: Receive buffer of SPI slave
 * @param[in] transferSize: Transfer size in byte
 * @return ERR_SUCCESS: The transfer is success
 *         ERR_BUSY: Cannot perform transfer because of a transfer is already in progress
 */
errcode_t SPI_SlaveTransferPolling(uint32_t instance,
                                   const uint8_t* txBuffer,
                                   uint8_t* rxBuffer,
                                   uint32_t transferSize);

/*!
 * @brief Get transfer status in non-blocking transfer: in progress or complete
 *
 * @param[in] instance: SPI instance
 * @param[out] bytesRemained: Pointer to a value that is filled in with the number of bytes
 *                            that must be received
 * @return ERR_SUCCESS: The transfer has completed successfully
 *         ERR_BUSY: The transfer is still in progress
 */
errcode_t SPI_SlaveGetTransferStatus(uint32_t instance, uint32_t* bytesRemained);

/*!
 * @brief Terminates a transfer during progress.
 *
 * @param[in] instance: SPI instance
 * @return None.
 */
void SPI_SlaveAbortTransfer(uint32_t instance);

/*!
 * @brief Register callback function and its parameter for spi instance
 *
 * @param[in] instance: SPI instance
 * @param[in] callback: Pointer to the callback function
 * @param[in] param: Pointer to the callback function's parameter
 * @return None
 */
void SPI_InstallCallback(uint32_t instance, spi_callback_t callback, void* param);

/*!
 * @brief SPI interrupt handler process function
 *
 * @param[in] instance: SPI instance
 * @return None
 */
void SPI_IRQHandlerProcess(uint32_t instance);

#if defined(__cplusplus)
}
#endif

/*! @} */

#endif /* SPI_DRV_H */

/*******EOF********************************************************************/

